package org.rency.ues.annotation.advice;

import org.apache.commons.lang.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.rency.ues.annotation.EncryptParameter;
import org.rency.ues.annotation.support.AnnotionData;
import org.rency.ues.annotation.support.MultiAnnotionData;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 加解密切面基类
 * @author: user_rcy@163.com
 * </br>Created on 2016年7月22日
 * @version: V1.0.0
 * @since JDK 1.8
 */
public class UesAdviceBase {

    /**
     * 加密注解参数构建
     * @param pjp
     * @param currentMethod
     * @param annotation
     * @return
     * @throws Throwable
     */
    protected MultiAnnotionData buildEncryptAnnotationData(ProceedingJoinPoint pjp, Method currentMethod, Annotation annotation) throws Throwable {
        MultiAnnotionData miad = new MultiAnnotionData();

        Annotation[][] paramAnnotations = currentMethod.getParameterAnnotations();
        if (paramAnnotations != null && paramAnnotations.length != 0) {
            Object[] args = pjp.getArgs();
            for (int i = 0; i < paramAnnotations.length; i++) {
                Annotation[] annotations = paramAnnotations[i];
                Object arg = args[i];
                if (ArrayUtils.isEmpty(annotations) || arg == null) {
                    continue;
                }
                AnnotionData ad = new AnnotionData();
                for (int j = 0; j < annotations.length; j++) {
                    Annotation paramAnnotation = annotations[j];
                    if (paramAnnotation instanceof EncryptParameter) {
                        handlerParameterSecurity(paramAnnotation, ad, arg);
                        break;
                    }
                }
                miad.addAnnotionData(ad);
            }
        }
        return miad;
    }

    /**
     * 加密参数处理
     * @param paramAnnotation
     * @param ad
     * @param arg
     * @throws Exception
     */
    private void handlerParameterSecurity(Annotation paramAnnotation, AnnotionData ad, Object arg) throws Exception {
        //获取加密字段
        String[] includeField = (String[]) quietGetFromAnnotation("includeField", paramAnnotation);
        if(ArrayUtils.isEmpty(includeField)){
            throw new IllegalArgumentException(paramAnnotation.getClass().getName()+" property 'includeField' is empty.");
        }
        ad.setIncludeFields(getFieldNames(arg, includeField));
        ad.setParameter(arg);
    }

    // 获取对象指定的字段
    private List<String> getFieldNames(Object arg, String[] fields) throws Exception {
        Object obj = getFieldObject(arg);
        List<String> includeField = new ArrayList<String>();
        if (obj != null) {
            for (String name : fields) {
                Class<?> cls = obj.getClass();
                for (; !cls.equals(Object.class); cls = cls.getSuperclass()) {
                    Field field = cls.getDeclaredField(name);
                    field.setAccessible(true);
                    if (field.getType().equals(String.class)) {
                        includeField.add(field.getName());
                    }
                }
            }
        }
        return includeField;
    }

    private Object getFieldObject(Object arg) {
        Object obj = null;
        if (arg instanceof Collection<?>) {
            Collection<?> col = (Collection<?>) arg;
            if (col != null && !col.isEmpty()) {
                Iterator<?> it = col.iterator();
                if (it.hasNext()) {
                    obj = it.next();
                }
            }
        } else if (arg != null && arg.getClass().isArray()) {
            Object arr[] = (Object[]) arg;
            if (arr.length > 0) {
                obj = arr[0];
            }
        } else {
            obj = arg;
        }
        return obj;
    }

    protected AnnotionData buildDecryptAnnotationData(ProceedingJoinPoint pjp, Method currentMethod, Annotation annotation, Object rest) throws Throwable {
        AnnotionData ad = new AnnotionData();

        String[] includeField = (String[]) quietGetFromAnnotation("includeField", annotation);

        if (ArrayUtils.isNotEmpty(includeField)) {
            ad.setIncludeFields(getFieldNames(rest, includeField));
        } else {
            ad.setIncludeFields(getFieldNames(rest));
        }
        ad.setParameter(rest);
        return ad;
    }

    // 获得对象所有的字段
    private List<String> getFieldNames(Object arg) throws Exception {
        Object obj = getFieldObject(arg);
        List<String> includeField = new ArrayList<String>();
        if (obj != null) {
            Class<?> cls = obj.getClass();
            for (; !cls.equals(Object.class); cls = cls.getSuperclass()) {
                Field[] fields = cls.getDeclaredFields();
                for (Field f : fields) {
                    f.setAccessible(true);
                    includeField.add(f.getName());
                }
            }
        }
        return includeField;
    }

    // 获得切点方法
    protected Method getCurrentMethod(ProceedingJoinPoint pjp) throws Throwable {
        Signature sig = pjp.getSignature();
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        msig = (MethodSignature) sig;
        Object target = pjp.getTarget();
        Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        return currentMethod;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    // 获得方法上的注解
    protected Annotation getMethodAnnotation(Method method, Class annotationClass) {
        return method.getAnnotation(annotationClass);
    }

    // 获得注解某个方法的值
    protected Object quietGetFromAnnotation(String methodName, Annotation annotation) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        if (annotation == null) {
            return null;
        }
        try {
            return annotation.annotationType().getDeclaredMethod(methodName).invoke(annotation);
        } catch (Exception e) {
            throw e;
        }
    }

}
